package com.samknows.measurement.activity.components;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
import org.achartengine.chart.PointStyle;
import org.achartengine.model.TimeSeries;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer.*;
import org.achartengine.util.MathHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.samknows.libcore.SKCommon;
import com.samknows.libcore.SKPorting;
import com.samknows.libcore.R;
import com.samknows.measurement.util.SKDateFormat;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.Html;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.TextView;
public class SKGraphForResults {
public enum DATERANGE_1w1m3m1y {
DATERANGE_1w1m3m1y_ONE_WEEK,
DATERANGE_1w1m3m1y_ONE_MONTH,
DATERANGE_1w1m3m1y_THREE_MONTHS,
DATERANGE_1w1m3m1y_ONE_YEAR,
DATERANGE_1w1m3m1y_ONE_DAY,
DATERANGE_1w1m3m1y_SIX_MONTHS // Only for the SK app...
}
private final String TAG = SKGraphForResults.class.getSimpleName();
private final ViewGroup containerViewCroup;
private String json;
private String date;
private String tag = "tag";
//
// achartengine (begin)
//
// http://stackoverflow.com/questions/8869854/how-to-implement-timechart-in-achartengine-with-android
private TimeSeries mTimeSeries;
private GraphicalView mGraphicalView = null;
private TextView mCaptionView = null;
XYMultipleSeriesRenderer multipleSeriesRenderer = null;
//private int mFillColorEnd = Color.argb(0xff, 0x6d, 0xad, 0xce);
private void createChartRendererSeriesAndView(Context context) {
// The color values are from the iOS version...
//int areaTopColor = Color.argb(0xff, 0xb8, 0xd3, 0xe1);
//SKApplication appInstance = SKApplication.getAppInstance();
//Context context = appInstance.getApplicationContext();
Resources resources = context.getResources();
int areaEndColor = resources.getColor(R.color.GraphColourTopAreaFill);
int lineColor = resources.getColor(R.color.GraphColourTopLine);
int gridLineColor = resources.getColor(R.color.GraphColourVerticalGridLine);
// Create the multiple-series renderer... you might have more than one series
// plotted at once, if you wanted, through this API...
// XYMultipleSeriesRenderer multipleSeriesRenderer = new XYMultipleSeriesRenderer();
multipleSeriesRenderer = new XYMultipleSeriesRenderer();
multipleSeriesRenderer.setApplyBackgroundColor(true);
multipleSeriesRenderer.setBackgroundColor(Color.WHITE); // This is the graph BACKGROUND color
multipleSeriesRenderer.setMarginsColor(Color.WHITE); // This is the color that SURROUNDS the graph
multipleSeriesRenderer.setAntialiasing(true);
multipleSeriesRenderer.setChartTitle("");
multipleSeriesRenderer.setClickEnabled(false);
// Always show both axes
multipleSeriesRenderer.setAxesColor(Color.LTGRAY);
//multipleSeriesRenderer.setAxesColor(Color.TRANSPARENT);
multipleSeriesRenderer.setLabelsColor(Color.BLACK);
multipleSeriesRenderer.setXLabelsColor(Color.BLACK);
multipleSeriesRenderer.setYLabelsColor(0, Color.BLACK); // 0 is the scale - a mystery value!
multipleSeriesRenderer.setYLabelsPadding(5);
multipleSeriesRenderer.setYLabelsAlign(Paint.Align.RIGHT);
// Needs to be fairly, to cater for e.g. 0.008 type values!
multipleSeriesRenderer.setLabelsTextSize(12);
NumberFormat format = NumberFormat.getNumberInstance();
format.setMaximumFractionDigits(3);
multipleSeriesRenderer.setLabelFormat(format);
//multipleSeriesRenderer.setChartValuesFormat(format);
multipleSeriesRenderer.setLegendTextSize(15);
multipleSeriesRenderer.setPanEnabled(false);
//multipleSeriesRenderer.setPointSize(3f);
multipleSeriesRenderer.setShowAxes(true);
multipleSeriesRenderer.setShowGridX(false);
multipleSeriesRenderer.setShowGridY(true);
multipleSeriesRenderer.setGridColor(gridLineColor);
multipleSeriesRenderer.setShowLegend(false);
multipleSeriesRenderer.setShowLabels(true);
multipleSeriesRenderer.setZoomButtonsVisible(false);
multipleSeriesRenderer.setZoomEnabled(false);
multipleSeriesRenderer.setZoomEnabled(false, false);
multipleSeriesRenderer.setAxisTitleTextSize(16);
multipleSeriesRenderer.setMargins(new int[]{20, 40, 15, 5}); // Pixels: top/left/bottom/right
multipleSeriesRenderer.setChartTitleTextSize(20);
// The next two lines prevent the Y axis zero from being suppressed!
multipleSeriesRenderer.setYAxisMin(0.0);
multipleSeriesRenderer.setYAxisMax(this.corePlotMaxValue); // * 1.05); // Allow border at top, for the points to draw in the case of 24-hour mod!
// http://stackoverflow.com/questions/13216619/android-chart-with-dates-on-x-axis?rq=1
// multipleSeriesRenderer.setXRoundedLabels(true);
//multipleSeriesRenderer.setYTitle(mYAxisTitle);
//multipleSeriesRenderer.setSelectableBuffer(20);
// Create the series renderer - we have just one of these.
XYSeriesRenderer seriesRenderer = new XYSeriesRenderer();
seriesRenderer.setColor(lineColor);
// if (mDateRange == DATERANGE_1w1m3m1y.DATERANGE_1w1m3m1y_ONE_DAY) {
// // 24-hour mode - just plot hourly averages!
// seriesRenderer.setPointStyle(PointStyle.CIRCLE);
// seriesRenderer.setFillPoints(true);
// seriesRenderer.setPointStrokeWidth(10);
//
// } else
{
// Tell the line to fill below!
FillOutsideLine fillOutsideLine = new FillOutsideLine(XYSeriesRenderer.FillOutsideLine.Type.BELOW);
fillOutsideLine.setColor(areaEndColor);
seriesRenderer.addFillOutsideLine(fillOutsideLine);
// TODO - might be possible to use a gradient at some point?
//seriesRenderer.setGradientEnabled(true);
//seriesRenderer.setGradientStart(0, areaTopColor);
//seriesRenderer.setGradientStop(0, areaEndColor);
// Show points, filled!
seriesRenderer.setPointStyle(PointStyle.CIRCLE);
//seriesRenderer.setFillPoints(true);
seriesRenderer.setPointStrokeWidth(8);
}
// Add the series renderer to the multiple series renderer...
multipleSeriesRenderer.addSeriesRenderer(seriesRenderer);
// Create a series, and it it to the dataset...
mTimeSeries = new TimeSeries("");
// Create a dataset, and add the series to it...
XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();
dataset.addSeries(mTimeSeries);
// Finally: create the chart graphical view!
if (mDateRange == DATERANGE_1w1m3m1y.DATERANGE_1w1m3m1y_ONE_DAY) {
// 24-hour format - just show time!
mGraphicalView = ChartFactory.getTimeChartView(context, dataset, multipleSeriesRenderer, SKDateFormat.sGetGraphTimeFormat());
// int lLabels = multipleSeriesRenderer.getXLabels();
// multipleSeriesRenderer.setXLabels(0);
// int i;
// for (i = 0; i < 5; i++) {
// multipleSeriesRenderer.addXTextLabel(i, "Wow!");
// }
//
multipleSeriesRenderer.setXLabels(0);
} else {
// 1 week or more - show date!
mGraphicalView = ChartFactory.getTimeChartView(context, dataset, multipleSeriesRenderer, SKDateFormat.sGetGraphDateFormat(context));
}
// Now that we have the chart, we'll be able to add-in some data...
// and add to the ViewGroup... straight after this call!
}
//
// Values extracted from the JSON data!
//
JSONObject jsonData = null;
ArrayList<Double> mpCorePlotDataPoints;
ArrayList<Date> mpCorePlotDates;
double corePlotMinValue;
double corePlotMaxValue;
String mYAxisTitle = "Mbps";
// For Mock Testing...
public ArrayList<Double> getCorePlotDataPoints() {
return mpCorePlotDataPoints;
}
// For Mock Testing...
public ArrayList<Date> getCorePlotDates() {
return mpCorePlotDates;
}
private void extractCorePlotData() {
if (mDateRange == DATERANGE_1w1m3m1y.DATERANGE_1w1m3m1y_ONE_DAY) {
// We're dealing with 24-hour data!
extractPlotDataAveragedByHourFor24Hours();
return;
}
//
// To get here, we're dealing with a time period more than 24-hours...
//
extractPlotDataAveragedByDay();
}
private void extractPlotDataAveragedByDay() {
//
// Dealing with a time period more than 24-hours...
//
// On Android, the data in jsonResults contains all the point
// data in the specified period.
// The data points received are from the start day, to the end day.
// However, values might not be present for any given day.
// The actually calculates the *average* values
// for any given day.
// The way that the old graph system seemed to work, is to start from a zero value.
// If a data point is missing, the value used is interpolated between the last received value,
// and the next value; if no value has yet been seen, then the value remains at zero.
ArrayList<Double> theNewArray = new ArrayList<>();
ArrayList<Date> theDateArray = new ArrayList<>();
//Log.d(getClass().getName(), "jsonData=" + jsonData.toString());
try {
String theStartDateString = jsonData.getString("start_date");
String theEndDateString = jsonData.getString("end_date");
mYAxisTitle = jsonData.getString("y_label");
Date theStartDate = new Date((Long.valueOf(theStartDateString)));
//Date theEndDate = new Date((Long.valueOf(theEndDateString)));
long daysBetween = (Long.valueOf(theEndDateString) - Long.valueOf(theStartDateString)) / (1000L * 60L * 60L * 24L);
daysBetween++;
if (daysBetween <= 0) {
SKPorting.sAssert(getClass(), false);
daysBetween = 1;
}
//Log.d(getClass().getName(), "daysBetween=" + daysBetween);
theNewArray.ensureCapacity((int) daysBetween);
theDateArray.ensureCapacity((int) daysBetween);
ArrayList<Integer> theCountArray = new ArrayList<>();
for (int i = 0; i < (int) daysBetween; i++) {
theNewArray.add(null);
theCountArray.add(1);
Calendar cTheCalendar = Calendar.getInstance();
cTheCalendar.setTime(theStartDate);
cTheCalendar.set(Calendar.HOUR_OF_DAY, 0);
cTheCalendar.set(Calendar.MINUTE, 0);
cTheCalendar.set(Calendar.SECOND, 0);
cTheCalendar.set(Calendar.MILLISECOND, 0);
cTheCalendar.add(Calendar.DAY_OF_MONTH, i);
theDateArray.add(i, cTheCalendar.getTime());
}
// We MUST INCLUDE the actual end date.
// For example, for one week: start date might be 01 Aug, end date might be 08 Aug ...
// 01 02 03 04 05 06 07 08
// ^ ^ ^ ^ ^ ^ ^ = 7 days!!!
JSONArray theResults = jsonData.getJSONArray("results");
Calendar cForStartDate = Calendar.getInstance();
cForStartDate.setTime(theStartDate);
// Round-off the time data...
// http://stackoverflow.com/questions/1908387/java-date-cut-off-time-information
cForStartDate.set(Calendar.HOUR_OF_DAY, 0);
cForStartDate.set(Calendar.MINUTE, 0);
cForStartDate.set(Calendar.SECOND, 0);
cForStartDate.set(Calendar.MILLISECOND, 0);
int lItems = theResults.length();
int lIndex = 0;
for (lIndex = 0; lIndex < lItems; lIndex++) {
JSONObject item = theResults.getJSONObject(lIndex);
String value = item.getString("value");
//value = "0.00499"; // TODO - for debug/testing only!!
String datetime = item.getString("datetime");
// Which day does this correspond to?!
Calendar c = Calendar.getInstance();
long milliseconds = Long.valueOf(datetime);
Date theDate = new Date(milliseconds);
c.setTime(theDate);
// Round-off the time data...
// http://stackoverflow.com/questions/1908387/java-date-cut-off-time-information
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
//Date theCDate = c.getTime();
int dayIndex = 0;
// boolean bCheckDateDayMatch = false;
// int checkIndex = 0;
// for (checkIndex = 0; checkIndex < daysBetween; checkIndex++) {
// if (theCDate.compareTo(theDateArray.get(checkIndex)) == 0) {
// bCheckDateDayMatch = true;
// dayIndex = checkIndex;
// break;
// }
// }
//
// if (bCheckDateDayMatch) {
// Log.d(getClass().getName(), "Found date day match ("+dayIndex+")");
// } else {
// Log.e(getClass().getName(), "ERROR: did NOT find date/day match");
long diff = c.getTimeInMillis() - cForStartDate.getTimeInMillis();
dayIndex = (int) (diff / (24L * 60L * 60L * 1000L));
// Debug / testing - could enable one of the following lines to verify that array bounds are handled appropriately.
// dayIndex = -1;
// dayIndex = 10011;
if (dayIndex < 0) {
SKPorting.sAssert(getClass(), false);
dayIndex = 0;
} else if (dayIndex >= theNewArray.size()) {
SKPorting.sAssert(getClass(), false);
dayIndex = theNewArray.size() - 1;
}
// }
//SimpleDateFormat format = new SimpleDateFormat("dd/MM");
//Log.d(getClass().getName(), "Read date for dayIndex=["+checkIndex+"], =" + format.format(c.getTime()) + ", value=" + value);
// Get the double value safely, irrespective of Locale
double doubleValue = SKCommon.sGetDecimalStringAnyLocaleAsDouble(value);
if (theNewArray.get(dayIndex) == null) {
// Nothing there yet!
theNewArray.set(dayIndex, doubleValue);
theCountArray.set(dayIndex, 1);
} else {
// Something there already found for this day - add up, and we convert to an average in a moment...
double currentTotal = theNewArray.get(dayIndex);
theNewArray.set(dayIndex, currentTotal + doubleValue);
theCountArray.set(dayIndex, 1 + theCountArray.get(dayIndex));
}
}
// Now, fix the values back to the averages...
for (lIndex = 0; lIndex < daysBetween; lIndex++) {
if (theCountArray.get(lIndex) > 1) {
if (theNewArray.get(lIndex) != null) {
theNewArray.set(lIndex, (theNewArray.get(lIndex) / (double) theCountArray.get(lIndex)));
}
}
// // achartengine displays weird values if they are too small...
// if (theNewArray.get(lIndex) != null) {
// if (theNewArray.get(lIndex) < 0.01) {
// theNewArray.set(lIndex, 0.01);
// }
// }
}
// final boolean bDoBackAndForwardFill = false;
//
// // To reach here, we have an array of items...
// if (bDoBackAndForwardFill == true) {
// // We must now interpolate!
// int theLastNonNilNumberAtIndex = -1;
// lItems = theNewArray.size();
//
// // To reach here, we have an array of items...
// // We must now interpolate!
// for (lIndex = 0; lIndex < daysBetween; lIndex++) {
//
// Double theObject = theNewArray.get(lIndex);
// if (theObject == null) {
// // This is our PLACEHOLDER!
// if (theLastNonNilNumberAtIndex == -1) {
// // Nothing we can do here!
// continue;
// }
//
// Double theNumberAtLastNonNilIndex = theNewArray.get(theLastNonNilNumberAtIndex);
//
// // Interpolate. Look FORWARD to the next number!
// // If none found, then simply copy forward.
// boolean bLookForwardFound = false;
//
// int lLookForwardIndex;
// for (lLookForwardIndex = lIndex + 1; ; lLookForwardIndex++) {
// if (lLookForwardIndex >= lItems)
// {
// break;
// }
//
// Double theLookForwardObject = theNewArray.get(lLookForwardIndex);
// if (theLookForwardObject != null) {
// Double theLookForwardNumber = theLookForwardObject;
// bLookForwardFound = true;
//
// // Calculate the value to use!
// double theDecimalLookForward = theLookForwardNumber;
// double theDecimalNumberAtLastNonNilIndex = theNumberAtLastNonNilIndex;
// double theInterpolatedValue = theDecimalNumberAtLastNonNilIndex + (theDecimalLookForward - theDecimalNumberAtLastNonNilIndex) * ((double)(lIndex - theLastNonNilNumberAtIndex)) / ((double)(lLookForwardIndex - theLastNonNilNumberAtIndex));
// theNewArray.set(lIndex, theInterpolatedValue);
// break;
// }
// }
//
// if (bLookForwardFound == false) {
// theNewArray.set(lIndex, theNewArray.get(theLastNonNilNumberAtIndex));
// }
//
// } else {
// theLastNonNilNumberAtIndex = lIndex;
// }
//
// }
// }
// Finally, find the minimum and maximum values, for scaling the plot!
corePlotMinValue = 0.0;
// bool bMinFound = false;
corePlotMaxValue = 0.0;
boolean bMaxFound = false;
int items = theNewArray.size();
for (lIndex = 0; lIndex < items; lIndex++) {
Double theObject = theNewArray.get(lIndex);
if (theObject != null) {
Double theNumber = theObject;
//theNumber = 0.00499; // TODO - this is for debug ONLY!
// If the value is 0.00999 or less, then treat as 0.0!
if (theNumber < 0.01) {
if (theNumber > 0) {
theNumber = 0.00;
theNewArray.set(lIndex, theNumber);
}
}
double theDouble = theNumber;
if (bMaxFound == false) {
corePlotMaxValue = theDouble;
bMaxFound = true;
}
if (theDouble > corePlotMaxValue) {
corePlotMaxValue = theDouble;
bMaxFound = true;
}
}
}
//Log.d(this.getClass().getName(), "All values extracted!");
} catch (JSONException e) {
SKPorting.sAssert(getClass(), false);
} catch (NullPointerException e) {
SKPorting.sAssert(getClass(), false);
}
// Allow enough range for the plots note to be chopped-off in the Y range!
corePlotMaxValue *= 1.10;
mpCorePlotDataPoints = theNewArray;
mpCorePlotDates = theDateArray;
}
private void extractPlotDataAveragedByHourFor24Hours() {
// In the 24-hour case, we must actually average the values BY HOUR!
// On Android, the data in jsonResults contains all the point
// data in the specified period.
// First we extract, and sort them in ascending order of date...
ArrayList<Double> theNewArray = new ArrayList<>();
ArrayList<Date> theDateArray = new ArrayList<>();
Log.w(getClass().getName(), "TODO - 24-hour case!");
//SKLogger.sAssert(this.getClass(), "TODO - 24-hour case!", false);
ArrayList<Pair<String, String>> arrayOfDateValues = new ArrayList<>();
JSONArray theResults;
try {
theResults = jsonData.getJSONArray("results");
mYAxisTitle = jsonData.getString("y_label");
int lItems = theResults.length();
int lIndex = 0;
for (lIndex = 0; lIndex < lItems; lIndex++) {
JSONObject item = theResults.getJSONObject(lIndex);
String value = item.getString("value");
//value = "0.00499"; // TODO - for debug/testing only!!
String datetime = item.getString("datetime");
Pair<String, String> dateValuePair = new Pair<>(datetime, value);
arrayOfDateValues.add(dateValuePair);
}
} catch (JSONException e) {
e.printStackTrace();
SKPorting.sAssert(this.getClass(), false);
return;
}
// To get here, we have all the 24-hour date items, stored as pairs,
// but not necessarily sorted. Now sort them in ascending order of date.
Collections.sort(arrayOfDateValues, new Comparator<Pair<String, String>>() {
@Override
public int compare(Pair<String, String> lhs, Pair<String, String> rhs) {
Date theDateLsh = new Date((Long.valueOf(lhs.first)));
Date theDateRsh = new Date((Long.valueOf(rhs.first)));
return theDateLsh.compareTo(theDateRsh);
}
});
// And just store this in a new value, as a reminder the values
// are now sorted.
ArrayList<Pair<String, String>> sortedArray24 = arrayOfDateValues;
// Now, group the values by HOUR!
ArrayList<Double> valuesByHour = new ArrayList<>();
ArrayList<Integer> itemsByHour = new ArrayList<>();
ArrayList<Date> datesByHour = new ArrayList<>();
final double oneDay = 24.0 * 60.0 * 60.0;
final double timeIntervalForOneHour = (oneDay / 24.0);
// Our dates are calculated based on "NOW"
Date dateNow = new Date();
Calendar cTheCalendar = Calendar.getInstance();
cTheCalendar.setTime(dateNow);
cTheCalendar.add(Calendar.HOUR, -24);
Date dateYesterday = cTheCalendar.getTime();
{
int hourIndex;
for (hourIndex = 0; hourIndex < 24; hourIndex++) {
datesByHour.add(cTheCalendar.getTime());
itemsByHour.add(0);
valuesByHour.add(null);
cTheCalendar.add(Calendar.HOUR, +1);
}
}
final boolean bDoBackAndForwardFill = false;
for (Pair<String, String> theDay : sortedArray24) {
Double theStartTimeInterval = SKCommon.sGetDecimalStringAnyLocaleAsDouble(theDay.first);
Date theDate = new Date((Long.valueOf(theDay.first)));
String theValue = theDay.second;
// You can enable the following for localization testing...
//theValue = theValue.replaceAll("\\.",",");
Double theResult = SKCommon.sGetDecimalStringAnyLocaleAsDouble(theValue);
// Milliseconds
long timeIntervalSinceStartMs = theDate.getTime() - dateYesterday.getTime();
// Seconds
long timeIntervalSinceStart = timeIntervalSinceStartMs / 1000;
// Are we in a new hour?
// If so, save the last value before continuing!
int hourIndex = (int) (timeIntervalSinceStart / timeIntervalForOneHour);
SKPorting.sAssert(getClass(), hourIndex >= 0);
SKPorting.sAssert(getClass(), hourIndex <= 23);
if (hourIndex < 0) {
hourIndex = 0;
}
if (hourIndex > 23) {
hourIndex = 23;
}
itemsByHour.set(hourIndex, itemsByHour.get(hourIndex) + 1);
Double valueAtHour = valuesByHour.get(hourIndex);
if (valueAtHour == null) {
valuesByHour.set(hourIndex, theResult);
} else {
valuesByHour.set(hourIndex, valueAtHour + theResult);
}
}
// Now run through, and calculate the averages.
{
int hourIndex;
for (hourIndex = 0; hourIndex < 24; hourIndex++) {
if (itemsByHour.get(hourIndex) > 0) {
Double theAverage = valuesByHour.get(hourIndex) / (double) (itemsByHour.get(hourIndex));
valuesByHour.set(hourIndex, theAverage);
}
}
}
// Finally, find the minimum and maximum values, for scaling the plot!
corePlotMinValue = 0.0;
// bool bMinFound = false;
corePlotMaxValue = 0.0;
boolean bMaxFound = false;
{
int hourIndex;
for (hourIndex = 0; hourIndex < 24; hourIndex++) {
Date theDate = datesByHour.get(hourIndex);
theDateArray.add(theDate);
Double theResult = valuesByHour.get(hourIndex);
if (theResult == null) {
theNewArray.add(theResult);
continue;
}
//theResult = 0.00499; // TODO - this is for debug ONLY!
// If the value is 0.00999 or less, then treat as 0.0!
if (theResult < 0.01) {
if (theResult > 0) {
theResult = 0.00;
}
}
if (bMaxFound == false) {
corePlotMaxValue = theResult;
bMaxFound = true;
}
if (theResult > corePlotMaxValue) {
corePlotMaxValue = theResult;
bMaxFound = true;
}
theNewArray.add(theResult);
}
}
// Allow enough range for the plots note to be chopped-off in the Y range!
corePlotMaxValue *= 1.10;
mpCorePlotDataPoints = theNewArray;
mpCorePlotDates = theDateArray;
//
// We now have all the 24-hour values!
//
}
private void addCorePlotDataToGraphicalView() {
int lDates = mpCorePlotDates.size();
SKPorting.sAssert(getClass(), mpCorePlotDataPoints.size() == mpCorePlotDates.size());
if (mDateRange == DATERANGE_1w1m3m1y.DATERANGE_1w1m3m1y_ONE_DAY) {
// 24-hour mode - just plot hourly averages!
// However, with AChart engine there is no way to show labels
// alternately, like we can on iOS.
// multipleSeriesRenderer.setXLabels(6);
multipleSeriesRenderer.setXLabels(12);
} else if (mDateRange == DATERANGE_1w1m3m1y.DATERANGE_1w1m3m1y_ONE_WEEK) {
// If week period, force more label items...
multipleSeriesRenderer.setXLabels(lDates);
} else if (multipleSeriesRenderer.getXLabels() < 5) {
multipleSeriesRenderer.setXLabels(5);
}
// With the chart engine used on iOS, if we don't provide items, the x-axis (time) labels are generated.
// However, on Android, the x-axis labels are generated only where there are data items provided!
// The best we can do on Android, is:
// - start plotting well off-screen.
// - Once we have a value, we can interpolate.
// - We cannot draw points!
int indexOfFirstFoundValue = -1;
int lIndex = 0;
for (lIndex = 0; lIndex < lDates; lIndex++) {
Double theValue = mpCorePlotDataPoints.get(lIndex);
if (theValue != null) {
indexOfFirstFoundValue = lIndex;
break;
}
}
int indexOfLastFoundValue = -1;
if (indexOfFirstFoundValue != -1) {
// At least one item found... look backwards for last value.
for (lIndex = lDates - 1; lIndex >= 0; lIndex--) {
Double theValue = mpCorePlotDataPoints.get(lIndex);
if (theValue != null) {
indexOfLastFoundValue = lIndex;
break;
}
}
}
for (lIndex = 0; lIndex < lDates; lIndex++) {
Double theValue = mpCorePlotDataPoints.get(lIndex);
if (theValue != null) {
double theDouble = theValue;
//mCurrentSeries.add(lIndex, theDouble);
mTimeSeries.add(mpCorePlotDates.get(lIndex), theDouble);
} else {
// Nothing there - use a magic value that isn't plotted!
// http://stackoverflow.com/questions/13025981/achartengine-renders-null-values
// We can (and must) do this ONLY if
// - there are not yet any values to plot from this point backwards
// - OR if there are no values from here on to plot.
// Otherwise, don't provide a data point - that allows the system to fill between plot points.
// That allows the graph to start and end at "zero" with no points plotted,
// and allows the graph to fill as required between the plotted points.
if (lIndex <= indexOfFirstFoundValue) {
// No values yet plotted.
mTimeSeries.add(mpCorePlotDates.get(lIndex), MathHelper.NULL_VALUE);
} else {
// Are there any values to come in the future?
if (lIndex >= indexOfLastFoundValue) {
mTimeSeries.add(mpCorePlotDates.get(lIndex), MathHelper.NULL_VALUE);
}
}
}
}
}
private void attachAchartEngine(Context context, ViewGroup inContainerViewGroup) {
// If the chart already exists, remove it!
if (mGraphicalView != null) {
inContainerViewGroup.removeView(mGraphicalView);
}
// Extract the query data from the jsonData!
extractCorePlotData();
// We can now create and add the chart!
createChartRendererSeriesAndView(context);
// And now put the data in the chart!
addCorePlotDataToGraphicalView();
//
// Finally - add the new chart to our view group!
//
inContainerViewGroup.addView(mGraphicalView);
}
//
// achartengine (end)
//
//
// Update the graph, and set the caption text...
//
//Handler handler = new Handler();
private void updateGraphAndCaption(Context context, ViewGroup inContainerViewGroup) {
// Update the graph...
attachAchartEngine(context, inContainerViewGroup);
// And update the caption text... ensuring that it appears in bold.
// For some reason, the Html.fromHtml... approach is required for this to work
// reliably!
// http://stackoverflow.com/questions/1529068/is-it-possible-to-have-multiple-styles-inside-a-textview
// mCaptionView.setText(mYAxisTitle);
mCaptionView.setText(Html.fromHtml("<b>" + mYAxisTitle + "</b>"));
// TODO: on the simulator, the graph doesn't always display the background properly the first time!
// handler.postDelayed(new Runnable() {
//
// @Override
// public void run() {
// containerViewCroup.invalidate();
// mGraphicalView.invalidate();
//
// }
// }, 100);
}
Context mContext = null;
public SKGraphForResults(Context context, ViewGroup inViewGroup, TextView inCaptionView, String inTag) {
mContext = context;
containerViewCroup = inViewGroup;
if (inViewGroup.getClass() == WebView.class) {
// If we're embedded in a web view - ensure the scrollbars are disabled,
// as they flash-up momentarily and are unsightly!
inViewGroup.setVerticalScrollBarEnabled(false);
inViewGroup.setHorizontalScrollBarEnabled(false);
// http://stackoverflow.com/questions/2527899/disable-scrolling-in-webview
// breakingart.com: This prevents the webview being scrolled by dragging!
inViewGroup.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return (event.getAction() == MotionEvent.ACTION_MOVE);
}
});
}
// Make the Y Axis Label appear manually - the one build into the library always
// appears vertically, so we use our own, appearing horizontally in the top-left corner
mCaptionView = inCaptionView;
this.tag = inTag;
}
/**
* Set the data to display
*
* @param data JSONObject
*/
// The JSONObject is like this:
// "start_date":"1381499507861",
// "end_date":"1382104307862",
// "results":[{"value":"0.979312","datetime":"1382099660000"},{"value":"3.570656","datetime":"1382099068000"}],
// "type":0,
// "y_label":"Mbps"
// For a one-day view, by default there will be just one value, which will be an average;
// so, depending on how the SQL works we'll probably want to pass-in list of point items that applied for the last 24 hours.
// Then, like on iOS, the graph processor will identify this, and specially extract those 24-hour point items for display.
// Alternatively, the graph *could* query that data directly?
public void updateGraphWithTheseResults(JSONObject data, DATERANGE_1w1m3m1y dateFilter) {
if (data == null) {
SKPorting.sAssert(getClass(), false);
return;
}
Log.v(TAG, "setData()");
json = data.toString();
jsonData = data;
mDateRange = dateFilter;
// Attach a new, updated graph!
updateGraphAndCaption(mContext, containerViewCroup);
}
public void updateGraphWithTheseResults(JSONObject data, DATERANGE_1w1m3m1y dateFilter,
int backgroundColor, int fillColor) {
// Set the fill colour BEFORE calling update graph...
//mFillColorEnd = fillColor;
updateGraphWithTheseResults(data, dateFilter);
// Now that we have the graph, we can update the colours associated with it!
multipleSeriesRenderer.setBackgroundColor(backgroundColor); // This is the graph BACKGROUND color
multipleSeriesRenderer.setMarginsColor(backgroundColor); // This is the color that SURROUNDS the graph
// TODO - use the fill colour etc.!
}
DATERANGE_1w1m3m1y mDateRange = DATERANGE_1w1m3m1y.DATERANGE_1w1m3m1y_ONE_WEEK;
/**
* Return the data in a json string
*
* @return
*/
public String getData() {
Log.v(TAG, "getData()");
//Log.v(TAG, json);
return json;
}
/**
* Return the date as a string
*/
public String getStartDate() {
Log.v(TAG, "getStartDate()");
return date;
}
}